<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class AES extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'aes:d {ciphertext} {ts} {pos=1 :融合使用的是1，平台使用的是5} {is_jsdk=1: web使用的是1，平台使用的是2}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '解密前后端交互密文';

    // 加密步长
    const auth_pos = 1;

    private  $_chars = array(
        '0',
        'a',
        'q',
        '1',
        'b',
        's',
        '2',
        'r',
        'c',
        '3',
        't',
        'd',
        '4',
        'u',
        'e',
        '5',
        'g',
        'v',
        '6',
        'f',
        'p',
        '7',
        'h',
        'w',
        '8',
        'o',
        'x',
        '9',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'y',
        'z'
    );

    private $_rand = '';
    // 随机字符串

    private $_key = '';
    /**
     * 初始化, $_rand 随机
     * $length  随机多少位
     */
    private  function _initRand($length = 10, $pos = 2)
    {
        if (empty($this->_rand)) {
            shuffle($this->_chars);
            $tmp = array_slice($this->_chars, 0, $length);
            $this->_rand = implode('', $tmp);

            // 生成Key
            $this->_key = '';
            $md5_value = str_split(md5($this->_rand, false), 1);
            $i = 0;
            do {
                $index = $i % count($md5_value);
                $this->_key .= $md5_value[$index];
                $i += $pos;
            } while (strlen($this->_key) < 16);
        }
    }

    /**
     * 获取KEY
     */
    public  function getKey($length = 10, $pos = 2)
    {
        $this->_initRand($length, $pos);
        return $this->_key;
    }

    /**
     * 获取随机字符串, 如果随机字符串已经生成，就不要再生成了
     */
    public  function getRand($length = 10, $pos = 2)
    {
        $this->_initRand($length, $pos);
        return $this->_rand;
    }

    /**
     * 手动设置随机字符串, 当重新设置rand需要强制初始化一次key
     * $pos 读取key的步长
     */
    public  function setRand($rand, $pos = 2)
    {
        $this->_rand = $rand;

        // 生成Key
        $this->_key = '';
        $md5_value = str_split(md5($this->_rand, false), 1);
        $i = 0;
        do {
            $index = $i % count($md5_value);
            $this->_key .= $md5_value[$index];
            $i += $pos;
        } while (strlen($this->_key) < 16);
    }

    /**
     * 向量
     */
    private function _iv()
    {
        $this->_initRand();
        $iv = '';
        $arr = str_split($this->_key, 1);
        for ($i = count($arr) - 1; $i >= 0; $i--) {
            $iv .= $arr[$i];
        }
        return $iv;
    }

    /**
     * ciphertext 密文
     * pos 步长
     * rand 随机数
     */
    public function dencrypt($ciphertext, $rand = '', $pos = 2)
    {
        if (!empty($rand)) {
            $this->setRand($rand, $pos);
        }

        $ciphertext = trim($ciphertext);
        $iv = $this->_iv();

        $cipher = 'AES-128-CBC';
        $decrypted = trim(openssl_decrypt(hex2bin($ciphertext), $cipher, $this->_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv));

        return $decrypted;
    }

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $ciphertext = $this->argument('ciphertext');
        $ciphertext = stripslashes($ciphertext);
        $ts = $this->argument('ts');
        $pos = (int)$this->argument('pos');
        $is_jsdk = (int)$this->argument('is_jsdk');

        if (1 == $pos) {
            if (1 == $is_jsdk) {
                echo $this->requestDecrypt($ciphertext, $ts);
                exit();
            } else {
                echo $this->dencrypt($ciphertext, $ts, self::auth_pos);
                exit();
            }
        } elseif (2 == $pos) {
            if (1 == $is_jsdk) {
                echo $this->requestEncrypt($ciphertext, $ts);
                exit();
            } else {
                echo $this->encrypt($ciphertext, $ts, self::auth_pos);
                exit();
            }
        }
        $md5_value = str_split(md5($ts, false), 1);
        $i = 0;
        $key = $iv = '';
        do {
            $index = $i % count($md5_value);
            $key .= $md5_value[$index];
            $i += $pos;
        } while (strlen($key) < 16);
        $arr = str_split($key, 1);
        for ($i = count($arr) - 1; $i >= 0; $i--) {
            $iv .= $arr[$i];
        }
        $cipher = 'AES-128-CBC';
        $decrypted = trim(openssl_decrypt(hex2bin($ciphertext), $cipher, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv));
        echo $decrypted;
    }

    /**
     * 加密
     * plaintext 明文
     * pos 步长
     * rand 随机数
     */
    public function encrypt($plaintext, $rand = '', $pos = 2)
    {
        if (!empty($rand)) {
            $this->setRand($rand, $pos);
        }

        $plaintext = trim($plaintext);
        $iv = $this->_iv();

        $cipher = 'AES-128-CBC';
        $plaintext .= str_repeat("\0", 16 - (strlen($plaintext) % 16));
        $encrypted = bin2hex(openssl_encrypt($plaintext, $cipher, $this->_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv));
        return $encrypted;
    }

    public function requestEncrypt($data, $key = null)
    {
        $base_e_str = $this->_base64UrlEncode($data);
        $t = $base_e_str . $key;
        //echo "{$t}\n";
        $len = strlen($t);
        $per = intval($len/6);
        //var_dump($per);
        if($per<2){
            return urlencode($t);
        }
        $per_0 = $per - 1;

        $i = 0;
        $t_len = $len;
        $arr_per_0 = [];
        $arr_per = [];
        $arr_last = [];
        $t_start = 0;
        while(true){
            if($i%2===0){
                if($t_len/$per_0>1){
                    $arr_per_0[] = substr($t, $t_start, $per_0);
                }else{
                    $arr_last[] = substr($t, $t_start); //第三个参数直到末端，不填
                    break;
                }
                $t_start += $per_0;
                $t_len = $t_len - $per_0;
            }else{

                if($t_len/$per>1){
                    $arr_per[] = substr($t, $t_start, $per);
                }else{
                    $arr_last[] = substr($t, $t_start);
                    break;
                }
                $t_start += $per;
                $t_len = $t_len - $per;
            }
            $i++;
        }

        return implode("", $arr_last) . implode("", $arr_per) . implode("", $arr_per_0);
    }

    /**
     * get传输的编码功能   因为客户端提交时，服务器自动url解码一次。base64的字符会被转换
     * @param unknown $data
     * @return string
     */
    private function _base64UrlEncode($data)
    {
        //$data = str_replace(array('+', '/'), array('-', '_'), base64_encode($data));
        $data = base64_encode($data);
        $data = strtr($data, '+/', '-_');
        // 默认情况下，取消对编码的填充，因为它不需要解码，并且保持URL不含%编码。
        $data = rtrim($data, '=');
        return $data;
    }

    /**
     * get传输的解码功能
     * @param unknown $data
     * @return string
     */
    private function _base64UrlDecode($data)
    {
        //return base64_decode(str_replace(array('-', '_'), array('+', '/'), $data));
        $data = strtr($data, '-_', '+/');
        return base64_decode($data);
    }

    /**
     * 请求解密
     * @param unknown $data
     * @return string|multitype:string unknown
     */
    public function requestDecrypt($data, $key = null)
    {
        $t = $data;
        //echo "{$t}\n";
        $len = strlen($t);
        //var_dump($len);
        $per = intval($len/6);

        if($per<2){
            $len_key = strlen($key);
            $base64_estr = substr($t, 0, $len-$len_key);
            return $this->_base64UrlDecode($base64_estr);
        }
        $per_0 = $per - 1;

        $i = 0;
        $t_len = $len;
        $arr_per_0_num = [];
        $arr_per_num = [];
        while(true){
            if($i%2===0){
                if($t_len/$per_0>1){
                    $arr_per_0_num[] = $per_0;
                }else{
                    break;
                }
                $t_len = $t_len - $per_0;
            }else{

                if($t_len/$per>1){
                    $arr_per_num[] = $per;
                }else{
                    break;
                }
                $t_len = $t_len - $per;
            }
            $i++;
        }

        //print_r($arr_per_0_num);
        //print_r($arr_per_num);
        $arr_per_0 = [];
        $arr_per = [];
        $t_start = 0;
        $t_count = count($arr_per_0_num);
        foreach($arr_per_0_num as $k=>$per_0){
            $t_start -= $per_0;
            $arr_per_0[$t_count-1-$k] = substr($t, $t_start, $per_0);
        }
        //ksort($arr_per_0);
        //print_r($arr_per_0);

        $t_count = count($arr_per_num);
        foreach($arr_per_num as $k=>$per){
            $t_start -= $per;
            $arr_per[$t_count-1-$k] = substr($t, $t_start, $per);
        }
        //ksort($arr_per);
        //print_r($arr_per);

        $last_str = substr($t, 0, $len+$t_start);
        //var_dump($last_str);

        $i = 0 ;
        $max = max(count($arr_per_0), count($arr_per));
        $str = "";
        for($i=0; $i<$max; $i++){
            if(isset($arr_per_0[$i])){
                $str .= $arr_per_0[$i];
            }

            if(isset($arr_per[$i])){
                $str .= $arr_per[$i];
            }
        }
        //var_dump($str);
        $str .= $last_str;
        $len_key = strlen($key);
        $str = substr($str, 0, $len-$len_key);

        return $this->_base64UrlDecode($str);
    }
}
